package org.evolAI.organism.code;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.evolAI.organism.domain.MemoryGroup;
import org.evolAI.organism.domain.Organism;
import org.evolAI.organism.domain.Step;
import org.evolAI.organism.domain.Step.Condition;
import org.evolAI.organism.domain.Step.StepTypes;
import org.evolAI.organism.domain.Code;

public class CodeExecutor {
	
	private Code code;
	private Organism org;
	private int pointer;
    private final Executor context;
    private int previousMarker = 0;
    private int maxSteps;

    //Working Memory
    public Map<Integer, Integer> numsMem = new HashMap<Integer, Integer>();
    public Map<Integer, String> stringsMem = new HashMap<Integer, String>();
    public Map<Integer, List<String>> listsMem = new HashMap<Integer, List<String>>();

    public CodeExecutor(Code code, Organism org, Executor context, int maxSteps) {
		this.code = code;
		this.org = org;
		this.pointer = 0;
		this.context = context;
		this.maxSteps = maxSteps;
	}

	public void execute(int depth) {
        Step curStep;
        do {
        	if (context.getStepCount() > maxSteps)
            {
                context.setStop(true);
                break;
            }

            if (pointer >= code.size() || context.isStop())
                break;

            context.setStepCount(context.getStepCount() + 1);

            curStep = code.get(pointer);
            if (curStep.isUnevaluated() || checkConditions(curStep.getCondition()))
                executeStep(curStep, depth);

            pointer++;
        }
        while (true);
	}

    private void executeStep(Step curStep, int depth) {
        switch (curStep.getStepType()) {
            case Add:
                add(curStep);
                break;
            case Sub:
                sub(curStep);
                break;
            case Load:
                load(curStep);
                break;
            case LoadLocal:
                loadLocal(curStep);
                break;
            case LoadIndirect:
                loadIndirect(curStep);
                break;
            case LoadPart:
                loadPart(curStep);
                break;
            case RecallRandom:
                recallRandom(curStep);
                break;
            case RecallEarliest:
                recallEarliest(curStep);
                break;
            case RecallLatest:
                recallLatest(curStep);
                break;
            case RecallStrongest:
                recallStrongest(curStep);
                break;
            case StoreLocal:
                storeLocal(curStep);
                break;
            case StoreIndirect:
                storeIndirect(curStep);
                break;
            case Remember:
                rememberLong();
                break;
            case Dup:
                dup(curStep);
                break;
            case Pop:
                pop(curStep);
                break;
            case ConvertUp:
                convertUp(curStep);
                break;
            case ConvertDown:
                convertDown(curStep);
                break;
            case Compare:
                evaluate(curStep);
                break;
            case JumpForward:
            	if (curStep.getDataInt() != null)
            		pointer += curStep.getDataInt();
                break;
            case JumpBackward:
            	if (curStep.getDataInt() != null)
            		pointer -= curStep.getDataInt();
                if (pointer < 0)
                    pointer = 0;
                break;
            case JumpNextMarker:
                gotoNextMarker();
                break;
            case JumpPrevMarker:
                pointer = previousMarker;
                //GotoPreviousMarker();
                break;
            case Branch:
            	if (curStep.getDataInt() != null)
            		context.execute(curStep.getDataInt(), depth);
                break;
            case BranchIndirect:
                if (!context.getNums().isEmpty())
                    context.execute(context.getNums().pop(), depth);
                break;
            case Return:
                pointer = code.size();
                break;
            case Marker:
                previousMarker = pointer;
                break;
            case AddToOutput:
                addToOutput(curStep);
                break;
        }
    }

    private void pop(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (!context.getNums().isEmpty())
                    context.getNums().pop();
                break;
            case String:
                if (!context.getStrings().isEmpty())
                    context.getStrings().pop();
                break;
            case List:
                if (!context.getLists().isEmpty())
                    context.getLists().pop();
                break;
        }
    }

    private void dup(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (!context.getNums().isEmpty()) {
                    Integer dupInt = context.getNums().peek();
                    context.getNums().push(dupInt);
                }
                break;
            case String:
                if (!context.getStrings().isEmpty()) {
                    String dupString = context.getStrings().peek();
                    context.getStrings().push(dupString);
                }
                break;
            case List:
                if (!context.getLists().isEmpty()) {
                    List<String> dupList = context.getLists().peek();
                    context.getLists().push(dupList);
                }
                break;
        }
    }

    private void addToOutput(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (!context.getNums().isEmpty())
                    org.addToOutput(context.getNums().pop().toString());
                break;
            case String:
                if (!context.getStrings().isEmpty())
                    org.addToOutput(context.getStrings().pop());
                break;
            case List:
                if (!context.getLists().isEmpty()) {
                    List<String> aList = context.getLists().pop();
                    StringBuilder sbString = new StringBuilder();
                    for (String s: aList)
                        sbString.append(s);
                    org.addToOutput(sbString.toString());
                }
                break;
        }
    }

    private void add(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (context.getNums().size() > 1) {
                    int num1 = context.getNums().pop();
                    int num2 = context.getNums().pop();
                    context.getNums().push(num1 + num2);
                }
                break;
            case String:
                if (context.getStrings().size() > 1) {
                    String str1 = context.getStrings().pop();
                    String str2 = context.getStrings().pop();
                    context.getStrings().push(str1 + str2);
                }
                break;
            case List:
                if (context.getLists().size() > 1) {
                    List<String> list1 = context.getLists().pop();
                    List<String> list2 = context.getLists().pop();
                    List<String> newList = new ArrayList<String>();
                    newList.addAll(list1);
                    newList.addAll(list2);
                    context.getLists().push(newList);
                }
                break;
        }
    }

    private void sub(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (context.getNums().size() > 1) {
                    int num1 = context.getNums().pop();
                    context.getNums().push(num1 - context.getNums().pop());
                }
                break;
        }
    }

    private void load(Step step) {
        switch (step.getDataType()) {
            case Int:
                context.getNums().push(step.getDataInt());
                break;
            case String:
                if (step.getDataString() != null)
                    context.getStrings().push(step.getDataString());
                break;
            case List:
                if (step.getDataList() != null)
                    context.getLists().push(step.getDataList());
                break;
        }
    }

    private void convertUp(Step step) {
        switch (step.getDataType()) {
            case String:
                if (!context.getNums().isEmpty())
                    context.getStrings().push(context.getNums().pop().toString());
                break;
            case List:
                if (!context.getStrings().isEmpty()) {
                    List<String> newList = new ArrayList<String>();
                    newList.add(context.getStrings().pop());
                    context.getLists().push(newList);
                }
                break;
        }
    }

    private void convertDown(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (!context.getStrings().isEmpty())
                {
            		String s = context.getStrings().pop();
            		if (!s.isEmpty()) {
	                	try {
	                		Integer newInt = Integer.valueOf(s);
	                		context.getNums().push(newInt);
	                	}
	                	catch (NumberFormatException e) {
	                		context.getNums().push((int) s.getBytes()[0]);
	                	}
            		}
                }
                break;
            case String:
                if (!context.getLists().isEmpty())
                {
                    StringBuilder sbList = new StringBuilder();
                    List<String> llist = context.getLists().pop();
                    for (String s: llist)
                    {
                        sbList.append(s);
                    }
                    context.getStrings().push(sbList.toString());
                }
                break;
        }
    }

    private void loadPart(Step step) {
        switch (step.getDataType()) {
            case Int:

                break;
            case String:
                if (!context.getNums().isEmpty() && !context.getStrings().isEmpty()) {
                    String s = context.getStrings().pop();
                    int n = context.getNums().pop();
                    if (n >= 0 && s != null && s.length() > n) {
                        context.getStrings().push(s.substring(n, n));
                    }
                }
                break;
            case List:
                if (!context.getNums().isEmpty() && !context.getLists().isEmpty()) {
                    List<String> l = context.getLists().pop();
                    int n = context.getNums().pop();
                    if (n >= 0 && l != null && l.size() > n) {
                        context.getStrings().push(l.get(n));
                    }
                }
                break;
        }
    }

    private void loadLocal(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (numsMem.containsKey(step.getDataInt())) {
                    context.getNums().push(numsMem.get(step.getDataInt()));
                }
                break;
            case String:
                if (stringsMem.containsKey(step.getDataInt())) {
                    context.getStrings().push(stringsMem.get(step.getDataInt()));
                }
                break;
            case List:
                if (listsMem.containsKey(step.getDataInt())) {
                    context.getLists().push(listsMem.get(step.getDataInt()));
                }
                break;
        }
    }

    private void loadIndirect(Step step) {
        if (!context.getNums().isEmpty()) {
        	try {
	            int index = context.getNums().pop();
	            switch (step.getDataType()) {
	                case Int:
	                    if (numsMem.containsKey(index)) {
	                        context.getNums().push(numsMem.get(index));
	                    }
	                    break;
	                case String:
	                    if (stringsMem.containsKey(index)) {
	                        context.getStrings().push(stringsMem.get(index));
	                    }
	                    break;
	                case List:
	                    if (listsMem.containsKey(index)) {
	                        context.getLists().push(listsMem.get(index));
	                    }
	                    break;
	            }
        	}
        	catch (NullPointerException ex) {
        		context.getStepCount();
        	}
        }
    }
    
    private void storeLocal(Step step) {
        switch (step.getDataType()) {
            case Int:
                if (!context.getNums().isEmpty()) {
                    numsMem.put(step.getDataInt(), context.getNums().pop());
                }
                break;
            case String:
                if (!context.getStrings().isEmpty()) {
                    stringsMem.put(step.getDataInt(), context.getStrings().pop());
                }
                break;
            case List:
                if (!context.getLists().isEmpty()) {
                    listsMem.put(step.getDataInt(), context.getLists().pop());
                }
                break;
        }
    }

    private void storeIndirect(Step step) {
        if (!context.getNums().isEmpty()) {
            int index = context.getNums().pop();
            switch (step.getDataType()) {
                case Int:
                    if (!context.getNums().isEmpty()) {
                        numsMem.put(index, context.getNums().pop());
                    }
                    break;
                case String:
                    if (!context.getStrings().isEmpty()) {
                        stringsMem.put(index, context.getStrings().pop());
                    }
                    break;
                case List:
                    if (!context.getLists().isEmpty()) {
                        listsMem.put(index, context.getLists().pop());
                    }
                    break;
            }
        }
    }
    
    private void rememberLong() {
        if (context.getStrings().size() > 1) {
            String key = context.getStrings().pop();
            org.rememberLong(key, context.getStrings().pop());
        }
    }

    private void recallRandom(Step step)
    {
        if (!context.getStrings().isEmpty())
        {
            String str1 = context.getStrings().pop();
            switch (step.getDataType())
            {
                case Int:

                    break;
                case String:
                    context.getStrings().push(org.recallRandom(str1));
                    break;
                case List:
                    if (org.getMemories().containsKey(str1)) {
                        MemoryGroup mems = org.getMemories().get(str1);
                        context.getLists().push(mems.getList());
                    }
                    break;
            }
        }
    }

    private void recallEarliest(Step step) {
        if (!context.getStrings().isEmpty()) {
            String str1 = context.getStrings().pop();
            switch (step.getDataType()) {
                case Int:

                    break;
                case String:
                    context.getStrings().push(org.recallEarliest(str1));
                    break;
                case List:
                    if (org.getMemories().containsKey(str1)) {
                        MemoryGroup mems = org.getMemories().get(str1);
                        context.getLists().push(mems.getListByDateEarliest());
                    }
                    break;
            }
        }
    }

    private void recallLatest(Step step) {
        if (!context.getStrings().isEmpty()) {
            String str1 = context.getStrings().pop();
            switch (step.getDataType()) {
                case Int:

                    break;
                case String:
                    context.getStrings().push(org.recallLatest(str1));
                    break;
                case List:
                    if (org.getMemories().containsKey(str1))
                    {
                        MemoryGroup mems = org.getMemories().get(str1);
                        context.getLists().push(mems.getListByDateLatest());
                    }
                    break;
            }
        }
    }

    private void recallStrongest(Step step) {
        if (!context.getStrings().isEmpty()) {
            String str1 = context.getStrings().pop();
            switch (step.getDataType()) {
                case Int:

                    break;
                case String:
                    context.getStrings().push(org.recallStrongest(str1));
                    break;
                case List:
                    if (org.getMemories().containsKey(str1)) {
                        MemoryGroup mems = org.getMemories().get(str1);
                        context.getLists().push(mems.getListByDateLatest());
                    }
                    break;
            }
        }
    }

    private void gotoNextMarker() {
        do {
            pointer++;
        }
        while (pointer < code.size() && code.get(pointer).getStepType() != StepTypes.Marker);
    }


    private boolean checkConditions(Condition condition) {
        if (!context.getConds().isEmpty()) {
            Condition aCond = context.getConds().pop();
            if (aCond == null)
                return true;
            if (aCond == condition)
            	return true;
            if (aCond == Condition.LessThanOrEqual && (condition == Condition.LessThan || condition == Condition.Equal))
            	return true;
            if (aCond == Condition.GreaterThanOrEqual && (condition == Condition.GreaterThan || condition == Condition.Equal))
            	return true;
            return false;
        }
        return true;
    }

    private void evaluate(Step step) {
        Condition cond = null;
        switch (step.getDataType()) {
            case Int:
            	try {
	                if ((context.getNums().size() > 1 && step.getDataInt() == 0) || (context.getNums().size() > 0 && step.getDataInt() != 0)) {
	                    int ival1, ival2;
	                    if (step.getDataInt() == 0)
	                        ival1 = context.getNums().pop();
	                    else
	                        ival1 = step.getDataInt();
	                    ival2 = context.getNums().pop();
	                    cond = compareInts(ival1, ival2);
	                }
	                break;
            	}
            	catch (Exception ex) {
            		context.getStepCount();
            	}
            case String:
                if ((context.getStrings().size() > 1 && step.getDataString() == null) || (context.getStrings().size() > 0 && step.getDataString() != null)) {
                    String sval1, sval2;
                    if (step.getDataString() == null)
                        sval1 = context.getStrings().pop();
                    else
                        sval1 = step.getDataString();
                    sval2 = context.getStrings().pop();
                    cond = compareStrings(sval1, sval2);
                }
                break;
            case List:
                if ((context.getLists().size() > 1 && step.getDataList() == null) || (context.getLists().size() > 0 && step.getDataList() != null)) {
                    List<String> lval1, lval2;
                    if (step.getDataList() == null)
                        lval1 = context.getLists().pop();
                    else
                        lval1 = step.getDataList();
                    lval2 = context.getLists().pop();
                    cond = compareLists(lval1, lval2);
                }
                break;
        }
        context.getConds().push(cond);
    }
    
	private static Condition compareInts(int int1, int int2) {
		Condition comp = null;
		int result = new Integer(int1).compareTo(new Integer(int2));
		if (result == 0)
			comp = Condition.Equal;
		if (result < 0 )
			comp = Condition.LessThan;
		if (result > 0)
			comp = Condition.GreaterThan;
		return comp;
	}

	private static Condition compareStrings(String string1, String string2) {
		Condition comp;
		if (string1.equals(string2))
			comp = Condition.Equal;
		else
			comp = Condition.NotEqual;
		return comp;
	}
	
    private static Condition compareLists(List<String> list1, List<String> list2)
    {
        if (list1.size() != list2.size())
            return Condition.NotEqual;
        else {
            for (int i = 0; i < list1.size(); i++) {
                if (!list1.get(i).equals(list2.get(i))) {
                    return Condition.NotEqual;
                }
            }
        }
        return null;
    }

}
